import { isNotEmpty, removeCollectionItem } from '../util/collectionUtil'
import { isFunction } from '../util/typeUtil'
import { getContextOrSetDefault, removeContext } from '../util/contextUtil'

export class EventAbility {
  static EVENT_HANDLER_HOLDER_KEY = '__eventHandlers__'

  constructor () {
    this.handlersMap = {}
    this.onceHandlersMap = {}
  }

  contextEventHandlerHolder (route = '') {
    return getContextOrSetDefault(
      EventAbility.EVENT_HANDLER_HOLDER_KEY,
      () => [],
      route
    )
  }

  clearContextEvent (route) {
    const handlerHolder = this.contextEventHandlerHolder(route)
    if (isNotEmpty(handlerHolder)) {
      handlerHolder.forEach(({ handler, eventName }) => {
        this.off(eventName, handler, route)
      })
      removeContext(EventAbility.EVENT_HANDLER_HOLDER_KEY, route)
    }
  }

  on (eventName, handler) {
    if (!isFunction(handler)) {
      return
    }
    this.#eventHandlers(eventName).add(handler)
    this.contextEventHandlerHolder().push({
      handler,
      eventName
    })
  }

  #eventHandlers (eventName) {
    return this.handlersMap[eventName] = this.handlersMap[eventName] || new Set()
  }

  #onceEventHandlers (eventName) {
    return this.onceHandlersMap[eventName] = this.onceHandlersMap[eventName] || new Set()
  }

  off (eventName, handler, route = '') {
    if (!handler && this.handlersMap[eventName]) {
      delete this.handlersMap[eventName]
      return
    }
    this.#eventHandlers(eventName).delete(handler)
    removeCollectionItem(this.contextEventHandlerHolder(route), (el) => {
      if (!handler) {
        return el.eventName === eventName
      }
      return el.eventName === eventName && el.handler === handler
    })
  }

  once (eventName, handler) {
    const onceHandlers = this.#onceEventHandlers(eventName)
    if (onceHandlers.has(handler)) {
      return
    }
    onceHandlers.add(handler)
    const newHandler = (...args) => {
      handler(...args)
      this.off(eventName, newHandler)
    }
    this.on(eventName, newHandler)
  }

  async emit (eventName, ...args) {
    const handlers = this.#eventHandlers(eventName)
    if (isNotEmpty(handlers)) {
      for (const handler of handlers) {
        await handler(...args)
      }
    }
  }
}